package us.luosl.lslt.collection;

import us.luosl.lslt.common.Page;
import us.luosl.lslt.lang.Tuple;
import us.luosl.lslt.lang.Tuple2;

import java.util.*;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 常用的集合工具
 */
public class CollectUtil {

    /**
     * 构造一个 ArrayList
     * @param items 带构造的数据集
     * @param <T> 数据集类型
     * @return ArrayList
     */
    public static <T> List<T> newArrayList(T ...items){
        List<T> result = new ArrayList<>(items.length);
        result.addAll(Arrays.asList(items));
        return result;
    }

    /**
     * 构造一个 HashSet
     * @param items 带构造的数据集
     * @param <T> 数据集类型
     * @return HashSet
     */
    public static <T> Set<T> newHashSet(T ...items){
        HashSet<T> set = new HashSet<>(items.length);
        set.addAll(Arrays.asList(items));
        return set;
    }

    /**
     * 集合构造字符串
     * @param data 数据集
     * @param separator 分隔符
     * @param skip 执行跳过的 Predicate
     * @param strFun 集合元素转字符串方法
     * @param <T> 集合类型
     * @return 拼接后的字符串
     */
    public static <T> String mkStr(Collection<T> data, String separator, Predicate<T> skip, Function<T, String> strFun){
        Stream<T> stream = data.stream();
        if(null != skip){
            stream = stream.filter(skip.negate());
        }
        StringBuilder builder = new StringBuilder();
        Iterator<String> itr = stream.map(strFun).iterator();
        while(itr.hasNext()){
            builder.append(itr.next());
            if(itr.hasNext()){
                builder.append(separator);
            }
        }
        return builder.toString();
    }

    /**
     * 集合构造字符串
     * @param data 数据集
     * @param separator 分隔符
     * @param strFun 集合元素转字符串方法
     * @param <T> 集合类型
     * @return 拼接后的字符串
     */
    public static <T> String mkStr(Collection<T> data, String separator, Function<T, String> strFun){
        return mkStr(data, separator, null, strFun);
    }

    /**
     * 构造字符串
     * @param data 数据集
     * @param separator 分隔符
     * @param <T> 集合类型
     * @return 拼接后的字符串
     */
    public static <T> String mkStr(Collection<T> data, String separator){
        return mkStr(data, separator, null, Objects::toString);
    }

    /**
     * 求集和中最大的top元素
     * @param collection collection
     * @param topN topN
     * @param <T> <T>
     * @return List<T>
     */
    public static <T extends Comparable<T>> Collection<T> maxTopN(Collection<T> collection, int topN){
        return maxTopN(collection, topN, Comparable::compareTo);
    }

    /**
     * 求集和中最小的 topN 个元素
     * @param collection collection
     * @param topN topN
     * @param <T> <T>
     * @return List<T>
     */
    public static <T extends Comparable<T>> Collection<T> minTopN(Collection<T> collection, int topN){
        return minTopN(collection, topN, Comparable::compareTo);
    }

    /**
     * 求集和中最大的top元素
     * 使用 最小堆实现该策略
     * @param collection collection
     * @param topN topN
     * @param comparator comparator
     * @param <T> <T>
     * @return List<T>
     */
    public static <T> Collection<T> maxTopN(Collection<T> collection, int topN, Comparator<T> comparator) {
        PriorityQueue<T> heap = new PriorityQueue<>(topN, comparator);
        collection.forEach(item -> {
            if(heap.size() < topN){
                heap.add(item);
            }else{
                if(comparator.compare(heap.peek(), item) < 0){
                    heap.poll();
                    heap.add(item);
                }
            }
        });
        return heap;
    }

    /**
     * 求集和中最小的 topN 个元素
     * @param collection collection
     * @param topN topN
     * @param comparator comparator
     * @param <T> <T>
     * @return List<T>
     */
    public static <T> Collection<T> minTopN(Collection<T> collection, int topN, Comparator<T> comparator) {
        return maxTopN(collection, topN, comparator.reversed());
    }

    /**
     * 截取集合的部分
     *
     * @param <T> 集合元素类型
     * @param list 被截取的数组
     * @param start 开始位置（包含）
     * @param end 结束位置（不包含）
     * @return 截取后的数组，当开始位置超过最大时，返回空的List
     */
    public static <T> List<T> sub(List<T> list, int start, int end) {
        if (list == null || list.isEmpty()) {
            return Collections.emptyList();
        }

        final int size = list.size();
        if (start < 0) {
            start += size;
        }
        if (end < 0) {
            end += size;
        }
        if (start == size) {
            return new ArrayList<>(0);
        }
        if (start > end) {
            int tmp = start;
            start = end;
            end = tmp;
        }
        if (end > size) {
            if (start >= size) {
                return new ArrayList<>(0);
            }
            end = size;
        }

        return list.subList(start, end);
    }

    /**
     * 集合内存分页
     * @param data 分页集合
     * @param currentPage 当前页
     * @param pageSize 分页大小
     * @param <T> 集合元素类型
     * @return 分页对象
     */
    public static <T> Page<T> page(List<T> data, int currentPage, int pageSize){
        if(currentPage < 1) {
            throw new IllegalArgumentException("the currentPage must than or eq 1");
        }
        if(pageSize < 1) {
            throw new IllegalArgumentException("the pageSize must than or eq 1");
        }
        int pageStart = (currentPage - 1) * pageSize;
        int pageEnd = pageStart + pageSize;
        List<T> subData = sub(data, pageStart, pageEnd);
        return new Page<>(subData, pageSize, currentPage, data.size());
    }

    /**
     * 集合去重
     * @param data 数据集
     * @param keyExtractor 去重 key 抽取
     * @param <T> 数据集类型
     * @param <K> key 类型
     * @return 去重后得数据集
     */
    public static <T, K> List<T> distinctBy(Collection<T> data, Function<T, K> keyExtractor) {
        Set<K> set = new HashSet<>();
        List<T> result = new ArrayList<>();

        for (T item: data) {
            if(set.add(keyExtractor.apply(item))){
                result.add(item);
            }
        }

        return result;
    }

    /**
     * 对数据集合进行分区
     * @param data 带分区数据集
     * @param size 每个分区数据大小
     * @param <T> 数据集类型
     * @return 分区后的集合 List<List<T>>
     */
    public static <T> List<List<T>> partition(List<T> data, int size) {
        List<List<T>> resultCollector = new ArrayList<>();
        int currentPage = 1;
        Page<T> page;
        do{
            page = page(data, currentPage, size);
            if(!page.isEmpty()){
                resultCollector.add(page.getData());
            }
            currentPage++;
        }while(page.getHasNextPage());

        return resultCollector;
    }

    /**
     * 根据条件移除集合种的数据，并返回被移除的集合
     * @param data 带移除的集合
     * @param removeCondition 移除条件
     * @param <T> 数据集类型
     * @return 被移除的数据集 List<T>
     */
    public static <T> List<T> remove(Collection<T> data, Predicate<T> removeCondition){
        List<T> removeCollector = new ArrayList<>();
        Iterator<T> iterator = data.iterator();
        while (iterator.hasNext()) {
            T item = iterator.next();
            if(removeCondition.test(item)) {
                removeCollector.add(item);
                iterator.remove();
            }
        }
        return removeCollector;
    }

    /**
     * 判断数据集 data1 是否在 数据集 data2 中
     * @param <T1> 左数据集泛型
     * @param <T2> 右数据集泛型
     * @param <K> 键泛型
     * @param data1 左数据集
     * @param data2 右数据集
     * @param leftKeyExtractor 左键生成策略
     * @param rightKeyExtractor 右键生成策略
     * @return Tuple2<List<T1>, List<T1>>
     */
    public static <T1, T2, K> Tuple2<List<T1>, List<T1>> exist(Collection<T1> data1,
                                                               Collection<T2> data2,
                                                               Function<T1, K> leftKeyExtractor,
                                                               Function<T2, K> rightKeyExtractor){

        Set<K> existSet = data2.stream().map(rightKeyExtractor).collect(Collectors.toSet());

        Map<Boolean, List<T1>> grouped = data1.stream()
                .collect(Collectors.groupingBy(item -> existSet.contains(leftKeyExtractor.apply(item))));

        return Tuple.of(
                grouped.getOrDefault(true, new ArrayList<>()),
                grouped.getOrDefault(false, new ArrayList<>())
        );
    }

    private static <T1, T2, K, R> List<R> doJoin(Collection<T1> data1,
                                           Collection<T2> data2,
                                           Function<T1, K> leftKeyExtractor,
                                           Function<T2, K> rightKeyExtractor,
                                           BiFunction<T1, Optional<List<T2>>, Stream<R>> merge) {
        Map<K, List<T2>> data2GroupedMap = data2.stream().collect(Collectors.groupingBy(rightKeyExtractor));

        return data1.stream().flatMap(item -> {
            Optional<List<T2>> rightList = Optional.ofNullable(data2GroupedMap.get(leftKeyExtractor.apply(item)));
            return merge.apply(item, rightList);
        }).collect(Collectors.toList());
    }

    /**
     * 左连接
     * @param data1 左数据集
     * @param data2 右数据集
     * @param leftKeyExtractor 左数据集 key 生成策略
     * @param rightKeyExtractor 右数据集 key 生成策略
     * @param <T1> 左数据集元素类型
     * @param <T2> 右数据集元素类型
     * @param <K> key 数据类型
     * @return 链接后的数据集 List<Tuple<T1, Optional<T2>>>
     */
    public static <T1, T2, K> List<Tuple2<T1, Optional<T2>>> leftJoin(Collection<T1> data1,
                                                                      Collection<T2> data2,
                                                                      Function<T1, K> leftKeyExtractor,
                                                                      Function<T2, K> rightKeyExtractor) {
        BiFunction<T1, Optional<List<T2>>, Stream<Tuple2<T1, Optional<T2>>>> merge = (leftItem, rightListOpt) -> {
            Stream<Optional<T2>> rightOpts = rightListOpt
                    .map(ls -> ls.stream().map(Optional::ofNullable))
                    .orElseGet(() -> Stream.of(Optional.empty()));

            return rightOpts.map(opt -> Tuple.of(leftItem, opt));
        };
        return doJoin(data1, data2, leftKeyExtractor, rightKeyExtractor, merge);
    }

    /**
     * 右连接
     * @param data1 左数据集
     * @param data2 右数据集
     * @param leftKeyExtractor 左数据集 key 生成策略
     * @param rightKeyExtractor 右数据集 key 生成策略
     * @param <T1> 左数据集元素类型
     * @param <T2> 右数据集元素类型
     * @param <K> key 数据类型
     * @return 链接后的数据集 List<Tuple<T1, Optional<T2>>>
     */
    public static <T1, T2, K> List<Tuple2<T2, Optional<T1>>> rightJoin(Collection<T1> data1,
                                                                       Collection<T2> data2,
                                                                       Function<T1, K> leftKeyExtractor,
                                                                       Function<T2, K> rightKeyExtractor){
        return leftJoin(data2, data1, rightKeyExtractor, leftKeyExtractor);
    }

    /**
     * 内连接
     * @param data1 左数据集
     * @param data2 右数据集
     * @param leftKeyExtractor 左数据集 key 生成策略
     * @param rightKeyExtractor 右数据集 key 生成策略
     * @param <T1> 左数据集元素类型
     * @param <T2> 右数据集元素类型
     * @param <K> key 数据类型
     * @return 链接后的数据集 List<Tuple<T1, Optional<T2>>>
     */
    public static <T1, T2, K> List<Tuple2<T1, T2>> innerJoin(Collection<T1> data1,
                                                             Collection<T2> data2,
                                                             Function<T1, K> leftKeyExtractor,
                                                             Function<T2, K> rightKeyExtractor){
        BiFunction<T1, Optional<List<T2>>, Stream<Tuple2<T1, T2>>> merge = (leftItem, rightListOpt) -> rightListOpt
                .orElseGet(Collections::emptyList)
                .stream()
                .map(rightItem -> Tuple.of(leftItem, rightItem));

        return doJoin(data1, data2, leftKeyExtractor, rightKeyExtractor, merge);
    }

    /**
     * 外连接
     * @param data1 左数据集
     * @param data2 右数据集
     * @param leftKeyExtractor 左数据集 key 生成策略
     * @param rightKeyExtractor 右数据集 key 生成策略
     * @param <T1> 左数据集元素类型
     * @param <T2> 右数据集元素类型
     * @param <K> key 数据类型
     * @return 链接后的数据集 List<Tuple<T1, Optional<T2>>>
     */
    public static <T1, T2, K> List<Tuple2<Optional<T1>, Optional<T2>>> outerJoin(Collection<T1> data1,
                                                                                 Collection<T2> data2,
                                                                                 Function<T1, K> leftKeyExtractor,
                                                                                 Function<T2, K> rightKeyExtractor){
        Stream<Tuple2<Optional<T1>, Optional<T2>>> leftStream = leftJoin(data1, data2, leftKeyExtractor, rightKeyExtractor)
                .stream()
                .map(tuple -> tuple.map1(Optional::of));

        Stream<Tuple2<Optional<T1>, Optional<T2>>> rightStream = rightJoin(data1, data2, leftKeyExtractor, rightKeyExtractor)
                .stream()
                .filter(t -> !t._2.isPresent())
                .map(tuple -> tuple.map1(Optional::of).reverse());

        return Stream.concat(leftStream, rightStream).collect(Collectors.toList());
    }


}
